#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip_var.h>
#include <netinet/tcp.h>
#include <netinet/tcpip.h>
#include <netinet/udp.h>
#include <netinet/udp_var.h>
#include <stdio.h>

#include "FwRule.h"
#include "FwEngine.h"

// private methods.
int matchAddr(char* pktIp, char* ruleIp);
int matchPort(int pktPort, int rulePort);
int matchProto(int pktProto, char* ruleProto);
void charToIpOctets(char* charIp, char* retn);
int chkOctets(char* pktIpOctets, char* ruleIpOctets, int netlen);

/**
 *  Matches the given packet against rules list passed in.
 *  Returns matched rule node, or null if none found
 */
fwRuleNode* matchPkt(const unsigned char* pkt, const int pktlen, const fwRuleList* rules){
	int matchSrc = 0;
	int matchDst = 0;
	int matchSport = 0;
	int matchDport = 0;
	int matchPktProto = 0;
	int matchIpv = 0;

	int found = 0;

	struct tcphdr tcph;
	struct udphdr udph;

	struct ip *iph = (struct ip*) pkt;
	fwRuleNode* curNode = rules->head->next;

	//Repeat match process for every node, until match found.
	do{
		matchSrc = 0;
		matchDst = 0;
		matchSport = 0;
		matchDport = 0;
		matchPktProto = 0;
		matchIpv = 0;

		if(iph->ip_v == 4){	//check for IPV4
			matchIpv = (curNode->ipv == 4);
			matchSrc = matchAddr((char*) inet_ntoa(iph->ip_src), curNode->srcIP);
			matchDst = matchAddr((char*) inet_ntoa(iph->ip_dst), curNode->dstIP);

			matchPktProto = matchProto((int)iph->ip_p, curNode->proto);

			if(iph->ip_p == 6){ //TCP
				tcph = ((struct tcpiphdr*)pkt)->ti_t;
				matchSport = matchPort(ntohs(tcph.th_sport), curNode->sPort);
				matchDport = matchPort(ntohs(tcph.th_dport), curNode->dPort);

			}else if(iph->ip_p == 17){ //UDP 
				udph = ((struct udpiphdr*)pkt)->ui_u;
				matchSport = matchPort(ntohs(udph.uh_sport), curNode->sPort);
				matchDport = matchPort(ntohs(udph.uh_dport), curNode->dPort);
			}else{	//unknown protocol.  fail unless rule does not specify ports
				matchSport = matchPort(-1, curNode->sPort);
				matchDport = matchPort(-1, curNode->dPort); 
			}

		}else if(iph->ip_v == 6){	//check for IPV6
			matchIpv = (curNode->ipv == 6);
			//for ipv6, we can't check others...  just pass
			matchSrc = 1;
			matchDst = 1;
			matchSport = 1;
			matchDport = 1;
			matchPktProto = 1;	
		}else{	//unknown ipversion
			matchIpv = 0;
		}

		//check for all the match conditions
		if(matchSrc && matchDst && matchSport && matchDport && matchPktProto && matchIpv){
			found = 1;
		}
		if(!found){	//move to next rule if not found
			curNode = curNode->next;
		}
	}
	while(curNode != rules->tail && found == 0);

	if(found){
		return curNode;
	}else{
		return 0;
	}
}

/**
 *  matches address of packet against specified in rule
 *  returns true/false
 */
int matchAddr(char* pktIp, char* ruleIp){
	int result = 0;
	int netlen = 0;
	char* netmaskaddr = 0;

	char pktIpOctets[4];
	char ruleIpOctets[4];

	if(strcmp(ruleIp, "any") == 0){	//if rule is "any", we match regardless of packet's ip
		return 1;	
	}else{
		netmaskaddr = strchr(ruleIp, '/');	
		if(netmaskaddr != 0){	//we have network mask bit set
			netlen = atoi(netmaskaddr+1);	//get the network mask bits

			//we need to convert to ip octets, so we can do bit by bit comparison
			charToIpOctets(pktIp, &pktIpOctets[0]);	
			charToIpOctets(ruleIp, &ruleIpOctets[0]);

			result = chkOctets(pktIpOctets, ruleIpOctets, netlen);	//check octets

		}else{	//no network mask bit.  we can do simple str comparison
			result = (strcmp(pktIp, ruleIp) == 0);	
		}
		return result;
	}
}

/**
 *  Check port
 *  Return true/false
 */
int matchPort(int pktPort, int rulePort){
	if(rulePort == 0){
		return 1;
	}else{
		return (pktPort == rulePort);	
	}
}

/**
 *  check protocol
 *  return true/false
 */
int matchProto(int pktProto, char* ruleProto){
	if(strcmp(ruleProto, "any") == 0){	//if rule specifies "any", we match against any protocol
		return 1;
	}else{	
		if(pktProto == 6){	//protocol 6 == tcp
			if(strcmp("tcp", ruleProto) == 0){
				return 1;
			}else{
				return 0;
			}
		}
		if(pktProto == 17){	//protocol 17 == udp
			if(strcmp("udp", ruleProto) == 0){
				return 1;
			}else{
				return 0;
			}	
		}
		return 0; //rule specifies protocol, but the packet has unknown protocol.  do not match
	}
}

/**
 *  convert ip string to four octets
 *  retn is filled with four bytes representing octets
 */
void charToIpOctets(char* charIp, char* retn){
	char* octet;

	retn[0] = atoi(charIp);		
	
	octet = strchr(charIp, '.') + 1;
	retn[1] = atoi(octet);

	octet = strchr(octet, '.') + 1;
	retn[2] = atoi(octet);
	
	octet = strchr(octet, '.') + 1;
	retn[3] = atoi(octet);	
}

/**
 *  checks ip address in octets, against rule ip.  only check up to netlen bits
 *  returns true/false
 */
int chkOctets(char* pktIpOctets, char* ruleIpOctets, int netlen){
	int numOctets = netlen / 8;  //check number of octets we can do simple comparison on
	int left = netlen % 8;	     //leftover bits needs to be compared separately

	while(numOctets > 0){	//check first numOctets as whole
		if(pktIpOctets[numOctets-1] != ruleIpOctets[numOctets-1]){
			return 0;
		}
		numOctets--;
	}
	if(left > 0){  //if there are extra bits left, check those individually

		//shift to right, essentially removing the insignificant bits in the octet
		char pktIpBits = pktIpOctets[netlen/8] >> (8 - left);
		char ruleIpBits = ruleIpOctets[netlen/8] >> (8 - left);

		if(pktIpBits == ruleIpBits){
			return 1;
		}else{
			return 0;
		}
	}else{
		return 1;
	}	
}
